package com.keqiang.chart.impl.pie.impl.normal

import android.graphics.Canvas
import android.graphics.Paint
import android.text.Layout
import android.text.TextPaint
import com.keqiang.chart.impl.pie.LabelGravity
import com.keqiang.chart.impl.pie.LabelPosition
import com.keqiang.chart.impl.pie.render.BaseLabelRender
import com.keqiang.chart.utils.ObjectPool
import com.keqiang.chart.utils.px
import com.keqiang.chart.utils.to360Range
import com.keqiang.chart.utils.toRadian
import com.keqiang.utils.PaintUtils
import com.keqiang.utils.PaintUtils.getChartHeight
import kotlin.math.*

/**
 * 饼状图标签绘制器
 *
 * @author Created by wanggaowan on 2022/8/8 14:36
 */
open class PieChartLabelRender(chart: PieChart) : BaseLabelRender<PieChart>(chart) {

    override fun onDraw(canvas: Canvas) {
        val dataList = chart.data
        if (dataList.isNullOrEmpty()) {
            return
        }

        val labelPosition = chart.labelPosition
        if (labelPosition is LabelPosition.Inside) {
            drawInSideLabel(chart, canvas, labelPosition, dataList)
            return
        }

        drawOutsideLabel(chart, canvas, labelPosition as LabelPosition.OutSide, dataList)
    }

    /**
     * 绘制内容位置在饼状图内部的标签
     */
    protected open fun drawInSideLabel(chart: PieChart, canvas: Canvas, labelPosition: LabelPosition.Inside, dataList: List<PieEntity>) {
        val (centerX, centerY) = chart.getCenter()
        val textPaint = ObjectPool.obtainPaint<TextPaint>(true)

        dataList.forEach {
            if (it.isDraw && it.drawLabel) {
                val centerAngle = it.drawStartAngle + it.drawSweepAngle / 2f
                val radius2 =
                    (it.drawRadius + sqrt(it.offsetDx.pow(2) + it.offsetDy.pow(2))) * labelPosition.offsetCenterPercent
                val x1 = centerX + cos(centerAngle.toRadian) * radius2
                val y1 = centerY + sin(centerAngle.toRadian) * radius2

                it.label?.let { label ->
                    textPaint.color = it.labelColor
                    textPaint.textSize = it.labelSize
                    val width = textPaint.measureText(label)
                    canvas.drawText(label, x1 - width / 2f, y1, textPaint)
                }

                it.secondLabel?.let { label ->
                    textPaint.color = it.secondLabelColor
                    textPaint.textSize = it.secondLabelSize
                    val width = textPaint.measureText(label)
                    canvas.drawText(label, x1 - width / 2f, y1 + textPaint.getChartHeight(), textPaint)
                }
            }
        }

        ObjectPool.recyclePaint(textPaint)
    }

    /**
     * 绘制内容位置在饼状图外部的标签
     */
    protected open fun drawOutsideLabel(chart: PieChart, canvas: Canvas, labelPosition: LabelPosition.OutSide, dataList: List<PieEntity>) {
        val (centerX, centerY) = chart.getCenter()
        val tempPath = ObjectPool.obtainPath()
        val tempRectF = ObjectPool.obtainRectF()
        val paint = ObjectPool.obtainPaint<Paint>()
        val textPaint = ObjectPool.obtainPaint<TextPaint>(true)

        dataList.forEach {
            if (it.isDraw && it.drawLabel) {
                val indicatorLine = labelPosition.lineFactory.invoke(it)
                val centerAngle = (it.drawStartAngle + it.drawSweepAngle / 2f).to360Range
                val centerRadian = centerAngle.toRadian
                var radius2 =
                    it.drawRadius + sqrt(it.offsetDx.pow(2) + it.offsetDy.pow(2)) + indicatorLine.offset
                // 指示线第一段起始X坐标
                var x1: Float
                // 指示线第一段起始Y坐标
                var y1: Float

                if (indicatorLine.drawStartCircle && indicatorLine.circleRadius > 0) {
                    // 绘制指示线起始端处的圆点
                    radius2 += indicatorLine.circleRadius
                    x1 =
                        centerX + cos(centerRadian) * radius2
                    y1 =
                        centerY + sin(centerRadian) * radius2
                    paint.style = Paint.Style.FILL
                    paint.color = indicatorLine.circleColor
                    canvas.drawCircle(x1, y1, indicatorLine.circleRadius, paint)
                    radius2 += indicatorLine.circleRadius
                }

                x1 =
                    centerX + cos(centerRadian) * radius2
                y1 =
                    centerY + sin(centerRadian) * radius2

                // 标签宽度
                val labelWidth: Float
                val labelHeight = if (it.label.isNullOrEmpty()) {
                    labelWidth = 0f
                    0f
                } else {
                    textPaint.textSize = it.labelSize
                    labelWidth = textPaint.measureText(it.label)
                    textPaint.getChartHeight(includePadding = false)
                }

                // 第二个标签宽度
                val secondLabelWidth: Float
                val secondLabelHeight =
                    if (it.secondLabel.isNullOrEmpty() || labelPosition.labelGravity == LabelGravity.END) {
                        secondLabelWidth = 0f
                        0f
                    } else {
                        textPaint.textSize = it.secondLabelSize
                        secondLabelWidth = textPaint.measureText(it.secondLabel)
                        textPaint.getChartHeight(includePadding = false)
                    }

                // 两个标签中的最大值
                var maxWidth = labelWidth.coerceAtLeast(secondLabelWidth)
                if (maxWidth < indicatorLine.minLength) {
                    maxWidth = indicatorLine.minLength
                } else if (indicatorLine.maxLength != null && maxWidth > indicatorLine.maxLength!!) {
                    maxWidth = indicatorLine.maxLength!!
                }

                // 为了确保文本不会与饼状图重叠，计算在饼状图半径基础上的补充半径
                val addedRadius = if (y1 < centerY) {
                    when (labelPosition.labelGravity) {
                        LabelGravity.TOP -> secondLabelHeight
                        LabelGravity.END -> labelHeight / 2f
                        else -> labelHeight
                    }
                } else {
                    when (labelPosition.labelGravity) {
                        LabelGravity.TOP -> labelHeight
                        LabelGravity.END -> labelHeight / 2f
                        else -> secondLabelHeight
                    }
                }

                // 指示线第一段最小长度为10 px
                val minOneLine = addedRadius * abs(sin(centerRadian))
                radius2 += if (minOneLine < 10f.px) 10f.px else minOneLine

                // 标签水平方向与指示线间距
                val labelHorizontalOffset = 10f.px
                val x2 = centerX + cos(centerRadian) * radius2
                val y2 = centerY + sin(centerRadian) * radius2
                val x3 = x2 + if (x2 >= centerX)
                    if (labelPosition.labelGravity == LabelGravity.END) {
                        // 10px为绘制在指示线尾部时，第二根线条默认长度
                        10f.px
                    } else {
                        labelHorizontalOffset + maxWidth
                    }
                else if (labelPosition.labelGravity == LabelGravity.END) {
                    (-10f).px
                } else {
                    -labelHorizontalOffset - maxWidth
                }

                tempPath.reset()
                tempPath.moveTo(x1, y1)
                tempPath.lineTo(x2, y2)
                tempPath.lineTo(x3, y2)

                paint.style = Paint.Style.STROKE
                paint.strokeWidth = indicatorLine.width
                paint.color = indicatorLine.color
                canvas.drawPath(tempPath, paint)

                it.label?.let { label ->
                    when (labelPosition.labelGravity) {
                        LabelGravity.TOP -> {
                            if (x3 - x2 > 0) {
                                tempRectF.set(x2 + labelHorizontalOffset, y2 - indicatorLine.width - labelHeight, x3, y2)
                            } else {
                                tempRectF.set(x3, y2 - indicatorLine.width - labelHeight, x2 - labelHorizontalOffset, y2)
                            }
                        }

                        LabelGravity.END -> {
                            if (x3 - x2 > 0) {
                                tempRectF.set(x3 + labelHorizontalOffset, y2 - labelHeight / 2f,
                                    x3 + maxWidth + labelHorizontalOffset, y2 + labelHeight / 2f)
                            } else {
                                tempRectF.set(x3 - maxWidth - labelHorizontalOffset, y2 - labelHeight / 2f,
                                    x3 - labelHorizontalOffset, y2 + labelHeight / 2f)
                            }
                        }

                        else -> {
                            if (x3 - x2 > 0) {
                                tempRectF.set(x2 + labelHorizontalOffset, y2, x3, y2 + indicatorLine.width + labelHeight)
                            } else {
                                tempRectF.set(x3, y2, x2 - labelHorizontalOffset, y2 + indicatorLine.width + labelHeight)
                            }
                        }
                    }

                    canvas.save()
                    canvas.translate(tempRectF.left, tempRectF.top)
                    val align = if (labelPosition.labelGravity == LabelGravity.END) {
                        if (x3 - x2 > 0) Layout.Alignment.ALIGN_NORMAL else Layout.Alignment.ALIGN_OPPOSITE
                    } else if (x3 - x2 > 0) Layout.Alignment.ALIGN_OPPOSITE else Layout.Alignment.ALIGN_NORMAL
                    textPaint.color = it.labelColor
                    textPaint.textSize = it.labelSize
                    PaintUtils.drawText(canvas, textPaint, label, maxWidth.toInt(), 1, align, true)
                    canvas.restore()
                }

                if (secondLabelHeight > 0) {
                    it.secondLabel?.let { label ->
                        when (labelPosition.labelGravity) {
                            LabelGravity.TOP -> {
                                if (x3 - x2 > 0) {
                                    tempRectF.set(x2 + labelHorizontalOffset, y2, x3, y2 + indicatorLine.width + secondLabelHeight)
                                } else {
                                    tempRectF.set(x3, y2, x2 - labelHorizontalOffset, y2 + indicatorLine.width + secondLabelHeight)
                                }
                            }

                            else -> {
                                if (x3 - x2 > 0) {
                                    tempRectF.set(x2 + labelHorizontalOffset, y2 - indicatorLine.width - secondLabelHeight, x3, y2)
                                } else {
                                    tempRectF.set(x3, y2 - indicatorLine.width - secondLabelHeight, x2 - labelHorizontalOffset, y2)
                                }
                            }
                        }

                        canvas.save()
                        canvas.translate(tempRectF.left, tempRectF.top)
                        val align =
                            if (x3 - x2 > 0) Layout.Alignment.ALIGN_OPPOSITE else Layout.Alignment.ALIGN_NORMAL
                        textPaint.color = it.secondLabelColor
                        textPaint.textSize = it.secondLabelSize
                        PaintUtils.drawText(canvas, textPaint, label, maxWidth.toInt(), 1, align, true)
                        canvas.restore()
                    }
                }
            }
        }

        ObjectPool.recyclePath(tempPath)
        ObjectPool.recycleRectF(tempRectF)
        ObjectPool.recyclePaint(paint)
        ObjectPool.recyclePaint(textPaint)
    }
}